
/*
Copyright 2009 Peter Ekler, Akos Ludanyi, Budapest University of Technology and Economics, Department of Automation and Applied Informatics, Applied Mobile Research Group (AMORG) http://amorg.aut.bme.hu

This file is part of MobTorrent.

MobTorrent is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

MobTorrent is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with MobTorrent.  If not, see <http://www.gnu.org/licenses/>.
*/

package sha1;


import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
 * SHA-1 hasher class.
 * @author Ludanyi Akos
 */
final public class SHA1 {
    final private int [] w = new int[80];
    final private byte [] end = new byte[64];

    private int a, b, c, d, e;
    private int length; //in bytes
    private int endLength;

    public SHA1()
    {
        reset();
    }

    public void reset()
    {
        endLength = 0;
        length = 0;

        a = 0x67452301;
        b = 0xEFCDAB89;
        c = 0x98BADCFE;
        d = 0x10325476;
        e = 0xC3D2E1F0;
    }

    /**
     * Add data to hash.
     * @param inp data
     */
    public void update(byte [] inp)
    {
        update(inp, 0, inp.length);
    }

    /**
     * Add data to hash.
     * @param inp data
     * @param  offset offset
     * @param inpLength lenth
     */

    public void update(byte [] inp, int offset, int inpLength)
    {
        length += inpLength;
        if(endLength + inpLength < 64)
        {
            System.arraycopy(inp, offset, end, endLength, inpLength);
            endLength += inpLength;
            return;
        }
        if(endLength > 0)
        {
            System.arraycopy(inp, offset, end, endLength, 64 - endLength);
            addChunk(end, 0);
            offset += 64 - endLength;
            inpLength -= 64 - endLength;
            endLength = 0;
        }


        while(inpLength >= 64)
        {
            addChunk(inp, offset);
            inpLength -= 64;
            offset += 64;
        }

        if(inpLength > 0)
        {
            System.arraycopy(inp, offset, end, 0, inpLength);
            endLength = inpLength;
        }
    }

    /**
     * Get the result of the hash, and reset SHA-1
     * @return result of the hashing
     */
    public int [] digest()
    {
        boolean started = false;
        if(endLength + 8 > 64)
        {
            started = true;
            end[endLength] = (byte)(1 << 7);
            for(int i = endLength + 1; i < 64; i++)
                end[i] = 0;
            addChunk(end, 0);
            endLength = 0;
        }

        for(int i = endLength; i < 64 - 8; i++)
        {
            end[i] = (!started) ? ((byte)(1 << 7)) : 0;
            started = true;
        }

        long outLength = (long)length * 8L;

        ByteArrayOutputStream os = new ByteArrayOutputStream(8);
        DataOutputStream dos = new DataOutputStream(os);

        try
        {
            dos.writeLong(outLength);
            System.arraycopy(os.toByteArray(), 0, end, 56, 8);
            dos.close();
            os.close();
        }
        catch(IOException e)
        {
        }

        addChunk(end, 0);

       int [] ret = new int[]{a, b, c, d, e};

        reset();

        return ret;

    }

    /**
     * Convertes result to a hex string.
     */
    static public String resultToString(int [] inp)
    {
        StringBuffer r = new StringBuffer(8 * 5);
        
        for(int i = 0; i < 5; i++)
        {
            final String tmp = Integer.toHexString(inp[i]).toUpperCase();
            for(int j = 0; j < 8 - tmp.length(); j++)
                r.append('0');
            r.append(tmp);
        }

        return r.toString();
    }

    /**
     * Convertes result to a hex string.
     */
    static public String resultToString(byte [] inp)
    {
        StringBuffer r = new StringBuffer(8 * 5);

        for(int i = 0; i < 20; i++)
        {
            final String digit = Integer.toHexString(inp[i] & 0xff).toUpperCase();
            
            if(digit.length() < 2)
            {
                r.append('0');
            }

            r.append(digit);
        }

        return r.toString();
    }

    /**
     * Converts result to a byte array.
     * @param inp SHA-1 result
     * @return byte array
     */
    static public byte [] resultToByte(int [] inp)
    {
        byte [] ret = null;
        try
        {
            ByteArrayOutputStream s = new ByteArrayOutputStream(32 * 5);
            DataOutputStream ds = new DataOutputStream(s);

            for(int i = 0; i < 5; i++)
                ds.writeInt(inp[i]);

            ret = s.toByteArray();
            ds.close();
            s.close();
        }
        catch(IOException e)
        {
        }
        return ret;
    }

    static public String resultToByteString(int [] inp)
    {
        StringBuffer ret = new StringBuffer(20);
        byte [] b = resultToByte(inp);

        for(int i = 0; i < 20; i++)
        {
            final int c = b[i] & 0xff;
            ret.append((char)c);
        }

        return ret.toString();

    }

    //Inlined now
    /*private int rotate(int what, int shift)
    {
        return what << shift | what >>> (32 - shift);
    }*/

    /*private void test()
    {
         System.out.println("a=" + Integer.toHexString(a) + " b=" +Integer.toHexString(b) +
            " c=" + Integer.toHexString(c) + " d=" + Integer.toHexString(d) +
            " e=" + Integer.toHexString(e));
    }*/

    private void addChunk(byte[] inp, int offset)
    {
        int t = 0;
        int tmp;
        int a = this.a,  b = this.b,  c = this.c,  d = this.d,  e = this.e;

        for(int i = offset; i < offset + 64; i += 4)
        {
            w[t++] =
                (inp[i] & 0xFF) << 24 |
                (inp[i + 1]  & 0xFF) << 16 |
                (inp[i + 2] & 0xFF) << 8 |
                (inp[i + 3] & 0xFF);
        }

        for(t = 16; t < 80; t++)
        {
            //w[t] = rotate(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1);
            tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
            w[t] = (tmp << 1) | (tmp >>> 31);
        }

        /*for(t = 0; t < 80; t++)
        {
        System.out.println("w[" + t + "] = " + Integer.toHexString(w[t]));
        }*/

        //int k = 0x5A827999;
        for(t = 0; t < 20; t++)
        {
            tmp = ((a << 5) | (a >>> 27)) + ((b & c) | ((~b) & d)) + e + w[t] + 0x5A827999;
            e = d;
            d = c;
            //c = rotate(b, 30);
            c = (b << 30) | (b >>> 2);
            b = a;
            a = tmp;
        }

        //k = 0x6ED9EBA1;
        for(t = 20; t < 40; t++)
        {
            //tmp = rotate(a, 5) + (b ^ c ^ d) + e + w[t] + k;
            tmp = ((a << 5) | (a >>> 27)) + (b ^ c ^ d) + e + w[t] + 0x6ED9EBA1;
            e = d;
            d = c;
            //c = rotate(b, 30);
            c = (b << 30) | (b >>> 2);
            b = a;
            a = tmp;
        }

        //k = 0x8F1BBCDC;
        for(t = 40; t < 60; t++)
        {
            //tmp = rotate(a, 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] + k;
            tmp = ((a << 5) | (a >>> 27)) + ((b & c) | (b & d) | (c & d)) + e + w[t] + 0x8F1BBCDC;
            e = d;
            d = c;
            //c = rotate(b, 30);
            c = (b << 30) | (b >>> 2);
            b = a;
            a = tmp;
        }

        //k = 0xCA62C1D6;
        for(t = 60; t < 80; t++)
        {
            //tmp = rotate(a, 5) + (b ^ c ^ d) + e + w[t] + k;
            tmp = ((a << 5) | (a >>> 27)) + (b ^ c ^ d) + e + w[t] + 0xCA62C1D6;
            e = d;
            d = c;
            //c = rotate(b, 30);
            c = (b << 30) | (b >>> 2);
            b = a;
            a = tmp;
        }


        this.a += a;
        this.b += b;
        this.c += c;
        this.d += d;
        this.e += e;
    }
}

